package lua

const defaultArrayCap = 32
const defaultHashCap = 32

type lValueArraySorter struct {
	L      *LState
	Fn     *LFunction
	Values []LValue
	len    int
}

func (lv lValueArraySorter) Len() int {
	return lv.len
}

func (lv lValueArraySorter) Swap(i, j int) {
	lv.Values[i], lv.Values[j] = lv.Values[j], lv.Values[i]
}

func (lv lValueArraySorter) Less(i, j int) bool {
	if lv.Fn != nil {
		lv.L.Push(lv.Fn)
		lv.L.Push(lv.Values[i])
		lv.L.Push(lv.Values[j])
		lv.L.Call(2, 1)
		return LVAsBool(lv.L.reg.Pop())
	}
	return lessThan(lv.L, lv.Values[i], lv.Values[j])
}

func isNil(data LValue) bool {
	return data == LNil || data == nil
}

func newLTable(acap int, hcap int) *LTable {
	if acap < 0 {
		acap = 0
	}
	if hcap < 0 {
		hcap = 0
	}
	tb := &LTable{}
	tb.Metatable = LNil
	tb.arraylen = 0
	tb.needUpdateKeys = false
	if acap != 0 {
		//tb.array = make([]LValue, 0, acap)
		tb.array = make([]LValue, acap, acap)
	}
	if hcap != 0 {
		tb.strdict = make(map[string]LValue, hcap)
	}
	return tb
}

// Len returns length of this LTable.
func (tb *LTable) Len() int {
	return tb.arraylen
}

func (tb *LTable) Resize() {
	if tb.array == nil {
		tb.array = make([]LValue, defaultArrayCap)
	} else {
		olen := len(tb.array)
		temp := make([]LValue, olen*2)
		copy(temp, tb.array)
		tb.array = temp
	}
	newlen := len(tb.array)
	for k, v := range tb.dict {
		if n, ok := k.(LNumber); ok {
			if int(n) <= newlen && n > 0 {
				tb.array[int(n)-1] = v
				delete(tb.dict, k)
			}
		}
	}
}

func (tb *LTable) Updatekeys() {
	if !tb.needUpdateKeys {
		return
	}
	if tb.keys == nil {
		tb.keys = []LValue{}
		tb.k2i = map[LValue]int{}
	}
	if tb.strdict != nil {
		for k, v := range tb.strdict {
			if v != LNil {
				lkey := LString(k)
				if _, ok := tb.k2i[lkey]; !ok {
					tb.k2i[lkey] = len(tb.keys)
					tb.keys = append(tb.keys, lkey)
				}
			}
		}
	}
	if tb.dict != nil {
		for k, v := range tb.dict {
			if v != LNil {
				if _, ok := tb.k2i[k]; !ok {
					tb.k2i[k] = len(tb.keys)
					tb.keys = append(tb.keys, k)
				}
			}
		}
	}
}

// Append appends a given LValue to this LTable.
func (tb *LTable) Append(value LValue) {
	if value == LNil {
		return
	}
	if tb.array == nil || len(tb.array) <= tb.arraylen {
		tb.Resize()
	}
	tb.array[tb.arraylen] = value
	newlen := tb.arraylen + 1
	for ; newlen < len(tb.array); newlen++ {
		if isNil(tb.array[newlen]) {
			break
		}
	}
	for {
		if tb.RawGetH(LNumber(newlen+1)) == LNil {
			break
		}
		newlen = newlen + 1
	}
	if newlen >= len(tb.array) {
		tb.Resize()
	}
	tb.arraylen = newlen
}

// Insert inserts a given LValue at position `i` in this table.
func (tb *LTable) Insert(i int, value LValue) {
	if i > tb.arraylen+1 || i < 0 {
		return
	}
	//移动后面的值
	for {
		o := tb.RawGetInt(i)
		tb.RawSetInt(i, value)
		if isNil(o) {
			break
		}
		value = o
		i++
	}
	//重新计算长度
	for {
		o := tb.RawGetInt(i + 1)
		if isNil(o) {
			break
		}
		i = i + 1
	}
	tb.arraylen = i
	if tb.arraylen >= len(tb.array) {
		tb.Resize()
	}
}

// Remove removes from this table the element at a given position.
func (tb *LTable) Remove(pos int) LValue {
	if pos > tb.arraylen || tb.arraylen == 0 {
		return LNil
	}
	oldval := tb.RawGetInt(pos)
	//移动后面的值
	for {
		nv := tb.RawGetInt(pos + 1)
		tb.RawSetInt(pos, nv)
		if isNil(nv) {
			break
		}
		pos = pos + 1
	}
	tb.arraylen = pos - 1
	return oldval
}

// RawSet sets a given LValue to a given index without the __newindex metamethod.
// It is recommended to use `RawSetString` or `RawSetInt` for performance
// if you already know the given LValue is a string or number.
func (tb *LTable) RawSet(key LValue, value LValue) {
	switch v := key.(type) {
	case LNumber:
		tb.RawSetInt(int(v), value)
		return
	case LString:
		tb.RawSetString(string(v), value)
		return
	}

	tb.RawSetH(key, value)
}

// RawSetInt sets a given LValue at a position `key` without the __newindex metamethod.
func (tb *LTable) RawSetInt(key int, value LValue) {
	if key < 1 || (key > len(tb.array) && key > tb.arraylen+1) {
		tb.RawSetH(LNumber(key), value)
		return
	}
	index := key - 1
	if isNil(value) {
		if key <= tb.arraylen {
			if key <= len(tb.array) {
				tb.array[index] = value
			} else {
				tb.RawSetH(LNumber(key), value)
			}
			tb.arraylen = index
			return
		}
		if tb.array == nil || key > len(tb.array) {
			tb.RawSetH(LNumber(key), value)
			return
		}
		tb.array[index] = value
		return
	}
	if key == tb.arraylen+1 {
		tb.Append(value)
		return
	}
	if tb.array == nil {
		if key <= defaultArrayCap {
			tb.Resize()
			tb.array[index] = value
			return
		}
	} else {
		if key <= len(tb.array) {
			tb.array[index] = value
			return
		} else if key <= 2*len(tb.array) {
			tb.Resize()
			tb.array[index] = value
			return
		}
	}
	tb.RawSetH(LNumber(key), value)
}

// RawSetString sets a given LValue to a given string index without the __newindex metamethod.
func (tb *LTable) RawSetString(key string, value LValue) {
	if tb.strdict == nil {
		tb.strdict = make(map[string]LValue, defaultHashCap)
	}

	if isNil(value) {
		// TODO tb.keys and tb.k2i should also be removed
		delete(tb.strdict, key)
	} else {
		tb.strdict[key] = value
		tb.needUpdateKeys = true
	}
}

// RawSetH sets a given LValue to a given index without the __newindex metamethod.
func (tb *LTable) RawSetH(key LValue, value LValue) {
	if s, ok := key.(LString); ok {
		tb.RawSetString(string(s), value)
		return
	}
	if tb.dict == nil {
		tb.dict = make(map[LValue]LValue, len(tb.strdict))
	}
	if isNil(value) {
		// TODO tb.keys and tb.k2i should also be removed
		delete(tb.dict, key)
	} else {
		tb.dict[key] = value
		tb.needUpdateKeys = true
	}
}

// RawGet returns an LValue associated with a given key without __index metamethod.
func (tb *LTable) RawGet(key LValue) LValue {
	switch v := key.(type) {
	case LNumber:
		return tb.RawGetInt(int(v))
	case LString:
		if tb.strdict == nil {
			return LNil
		}
		if ret, ok := tb.strdict[string(v)]; ok {
			return ret
		}
		return LNil
	}
	if tb.dict == nil {
		return LNil
	}
	if v, ok := tb.dict[key]; ok {
		return v
	}
	return LNil
}

// RawGetInt returns an LValue at position `key` without __index metamethod.
func (tb *LTable) RawGetInt(key int) LValue {
	if tb.array == nil || key > len(tb.array) || key < 1 {
		return tb.RawGetH(LNumber(key))
	}
	v := tb.array[key-1]
	if v == nil {
		return LNil
	}
	return v
}

// RawGet returns an LValue associated with a given key without __index metamethod.
func (tb *LTable) RawGetH(key LValue) LValue {
	if s, sok := key.(LString); sok {
		if tb.strdict == nil {
			return LNil
		}
		if v, vok := tb.strdict[string(s)]; vok {
			return v
		}
		return LNil
	}
	if tb.dict == nil {
		return LNil
	}
	if v, ok := tb.dict[key]; ok {
		return v
	}
	return LNil
}

// RawGetString returns an LValue associated with a given key without __index metamethod.
func (tb *LTable) RawGetString(key string) LValue {
	if tb.strdict == nil {
		return LNil
	}
	if v, vok := tb.strdict[string(key)]; vok {
		return v
	}
	return LNil
}

// ForEach iterates over this table of elements, yielding each in turn to a given function.
func (tb *LTable) ForEach(cb func(LValue, LValue)) {
	index := 0
	for ; index < tb.arraylen; index++ {
		key := index + 1
		v := tb.RawGetInt(key)
		cb(LNumber(key), v)
	}
	for ; index < len(tb.array); index++ {
		v := tb.array[index]
		if !isNil(v) {
			cb(LNumber(index+1), v)
		}
	}
	if tb.strdict != nil {
		for k, v := range tb.strdict {
			if v != LNil {
				cb(LString(k), v)
			}
		}
	}
	if tb.dict != nil {
		for k, v := range tb.dict {
			if v != LNil {
				cb(k, v)
			}
		}
	}
}

// This function is equivalent to lua_next ( http://www.lua.org/manual/5.1/manual.html#lua_next ).
func (tb *LTable) Next(key LValue) (LValue, LValue) {
	tb.Updatekeys()
	init := false
	if key == LNil {
		key = LNumber(0)
		init = true
	}

	kv, ok := key.(LNumber)
	if ok {
		if kv > 0 || init {
			int_kv := int(kv)
			if int_kv < tb.arraylen {
				return kv + 1, tb.RawGetInt(int_kv + 1)
			}
			for i := int_kv; i < len(tb.array); i++ {
				v := tb.array[i]
				if !isNil(v) {
					return LNumber(i + 1), v
				}
			}
			if int_kv == tb.arraylen || int_kv <= len(tb.array) {
				init = true
			}
		}
	}
	i := 0
	if !init {
		i = tb.k2i[key] + 1
	}
	for ; i < len(tb.keys); i++ {
		key := tb.keys[i]
		if v := tb.RawGetH(key); v != LNil {
			return key, v
		}
	}
	return LNil, LNil
}
